package com.vhans.db.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.vhans.core.domain.*;
import com.vhans.core.redis.RedisService;
import com.vhans.core.utils.HTMLUtils;
import com.vhans.core.web.model.dto.CommentDTO;
import com.vhans.core.web.model.dto.MailDTO;
import com.vhans.db.mapper.BCommentMapper;
import com.vhans.db.mapper.BQuizMapper;
import com.vhans.db.mapper.BRecordMapper;
import com.vhans.db.mapper.BUserMapper;
import com.vhans.db.model.dto.CheckDTO;
import com.vhans.db.model.dto.ConditionDTO;
import com.vhans.db.model.vo.comment.*;
import com.vhans.db.service.IBCommentService;
import com.vhans.db.service.SiteConfigService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.vhans.core.constant.CommonConstant.*;
import static com.vhans.core.constant.MqConstant.*;
import static com.vhans.core.constant.RedisConstant.COMMENT_LIKE_COUNT;
import static com.vhans.core.enums.CommentTypeEnum.*;

/**
 * 评论业务接口实现类
 *
 * @author vhanss
 */
@Service
public class BCommentServiceImpl implements IBCommentService {

    @Value("${agreement.url}")
    private String websiteUrl;

    @Autowired
    private BCommentMapper commentMapper;

    @Autowired
    private BRecordMapper recordMapper;

    @Autowired
    private BQuizMapper quizMapper;

    @Autowired
    private BUserMapper userMapper;

    @Autowired
    private SiteConfigService siteConfigService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public List<CommentVO> listCommentVO(ConditionDTO condition) {
        // 查询评论列表
        List<CommentVO> commentVOS = commentMapper.listCommentVO(condition);
        // 封装评论信息
        commentVOS.forEach(item -> {
            // 查询点赞量
            Integer likeNumber = redisService.getHash(COMMENT_LIKE_COUNT, item.getId().toString());
            // 设置当前点赞量为 持久点赞量 + 缓存点赞量
            item.setLikeNumber(item.getLikeNumber() + Optional.ofNullable(likeNumber).orElse(0));
        });
        return commentVOS;
    }

    @Override
    public void addComment(CommentDTO comment) {
        // 校验评论参数
        verifyComment(comment);
        SiteConfig siteConfig = siteConfigService.getSiteConfig();
        Integer commentCheck = siteConfig.getCommentCheck();
        // 过滤标签
        comment.setContent(HTMLUtils.filter(comment.getContent()));
        BComment newComment = BComment.builder()
                .fromUid(StpUtil.getLoginIdAsInt())
                .toUid(comment.getToUid())
                .typeId(comment.getTypeId())
                .type(comment.getType())
                .parentId(comment.getParentId())
                .replyId(comment.getReplyId())
                .content(comment.getContent())
                .isCheck(commentCheck.equals(FALSE) ? TRUE : FALSE)
                .build();
        // 保存评论
        commentMapper.insert(newComment);
        // 查询评论用户昵称
        String fromNickname = userMapper.selectOne(new LambdaQueryWrapper<BUser>()
                        .select(BUser::getNickname)
                        .eq(BUser::getId, StpUtil.getLoginIdAsInt()))
                .getNickname();
        if (siteConfig.getEmailNotice().equals(TRUE)) {
            // 异步通知用户
            CompletableFuture.runAsync(() -> {
                System.out.println("你好");
                notice(newComment, fromNickname);
            });
        }
    }

    @Transactional
    @Override
    public void updateCommentCheck(CheckDTO check) {
        // 修改评论审核状态
        check.getIdList().forEach(item ->
                commentMapper.updateById(BComment.builder().id(item).isCheck(check.getIsCheck()).build()));

    }

    @Override
    public List<RecentCommentVO> listRecentCommentVO() {
        List<RecentCommentVO> recentCommentVOS = commentMapper.selectRecentComment();
        recentCommentVOS.forEach(item -> {
            // 查询点赞量
            Integer likeNumber = redisService.getHash(COMMENT_LIKE_COUNT, item.getId().toString());
            // 设置当前点赞量为 持久点赞量 + 缓存点赞量
            item.setLikeNumber(item.getLikeNumber() + Optional.ofNullable(likeNumber).orElse(0));
        });
        return recentCommentVOS;
    }

    @Override
    public List<CommentHomeVO> listCommentHomeVO(ConditionDTO condition) {
        // 查询父评论
        List<CommentHomeVO> commentHomeVOList = commentMapper.selectParentComment(condition);
        if (CollectionUtils.isEmpty(commentHomeVOList)) {
            return new ArrayList<>();
        }
        // 父评论id集合
        List<Integer> parentCommentIdList = commentHomeVOList.stream()
                .map(CommentHomeVO::getId).collect(Collectors.toList());
        // 分组查询每组父评论下的子评论前三条
        List<ReplyVO> replyVOList = commentMapper.selectReplyByParentIdList(parentCommentIdList);
        // 封装子评论点赞量
        replyVOList.forEach(item -> {
            // 查询子评论点赞量
            Integer likeNumber = redisService.getHash(COMMENT_LIKE_COUNT, item.getId().toString());
            // 设置当前点赞量为 持久点赞量 + 缓存点赞量
            item.setLikeNumber(item.getLikeNumber() + Optional.ofNullable(likeNumber).orElse(0));
        });
        // 根据父评论id分组生成对应子评论的Map(parent->List)
        Map<Integer, List<ReplyVO>> replyMap = replyVOList.stream()
                .collect(Collectors.groupingBy(ReplyVO::getParentId));
        // 父评论的回复数量
        List<ReplyNumberVO> replyNumberList = commentMapper.selectReplyNumberByParentId(parentCommentIdList);
        // 转换Map
        Map<Integer, Integer> replyNumberMap = replyNumberList.stream()
                .collect(Collectors.toMap(ReplyNumberVO::getCommentId, ReplyNumberVO::getReplyNumber));
        // 封装评论数据
        commentHomeVOList.forEach(item -> {
            // 查询父评论点赞量
            Integer likeNumber = redisService.getHash(COMMENT_LIKE_COUNT, item.getId().toString());
            // 设置当前点赞量为 持久点赞量 + 缓存点赞量
            item.setLikeNumber(item.getLikeNumber() + Optional.ofNullable(likeNumber).orElse(0));
            item.setReplyVOList(replyMap.get(item.getId()));
            item.setReplyNumber(Optional.ofNullable(replyNumberMap.get(item.getId())).orElse(0));
        });
        return commentHomeVOList;
    }

    @Override
    public List<ReplyVO> listReply(Integer commentId) {
        // 查询子评论
        List<ReplyVO> replyVOList = commentMapper.selectReplyByParentId(commentId);
        replyVOList.forEach(item -> {
            // 查询子评论点赞量
            Integer likeNumber = redisService.getHash(COMMENT_LIKE_COUNT, item.getId().toString());
            // 设置当前点赞量为 持久点赞量 + 缓存点赞量
            item.setLikeNumber(item.getLikeNumber() + Optional.ofNullable(likeNumber).orElse(0));
        });
        return replyVOList;
    }

    @Override
    public void removeByIds(List<Integer> commentIdList) {
        commentMapper.deleteBatchIds(commentIdList);
    }

    /**
     * // 校验评论参数
     * @param comment 评论信息
     */
    private void verifyComment(CommentDTO comment) {
        if (comment.getType().equals(RECORD.getType())) {
            BRecord record = recordMapper.selectOne(new LambdaQueryWrapper<BRecord>()
                    .select(BRecord::getId)
                    .eq(BRecord::getId, comment.getTypeId()));
            Assert.notNull(record, "记录不存在");
        }
        if (comment.getType().equals(QUIZ.getType())) {
            BQuiz quiz = quizMapper.selectOne(new LambdaQueryWrapper<BQuiz>()
                    .select(BQuiz::getId)
                    .eq(BQuiz::getId, comment.getTypeId()));
            Assert.notNull(quiz, "题目不存在");
        }
        Optional.ofNullable(comment.getParentId()).ifPresent(parentId -> {  // 评论为子评论，判断信息
            // 判断父评论是否存在
            BComment parentComment = commentMapper.selectOne(new LambdaQueryWrapper<BComment>()
                    .select(BComment::getId, BComment::getParentId, BComment::getType)
                    .eq(BComment::getId, parentId));
            Assert.notNull(parentComment, "父评论不存在");
            Assert.isNull(parentComment.getParentId(), "当前评论为子评论，不能作为父评论");
            Assert.isTrue(comment.getType().equals(parentComment.getType()),
                    "只能以同类型的评论作为父评论");
            // 判断回复的评论和用户是否存在
            BComment replyComment = commentMapper.selectOne(new LambdaQueryWrapper<BComment>()
                    .select(BComment::getId, BComment::getParentId, BComment::getFromUid, BComment::getType)
                    .eq(BComment::getId, comment.getReplyId()));
            BUser toUser = userMapper.selectOne(new LambdaQueryWrapper<BUser>()
                    .select(BUser::getId).eq(BUser::getId, comment.getToUid()));
            Assert.notNull(replyComment, "回复的评论不存在");
            Assert.notNull(toUser, "回复的用户不存在");
            Assert.isTrue(comment.getType().equals(replyComment.getType()),
                    "只能回复同类型的下的评论");
            if (Objects.nonNull(replyComment.getParentId())) {
                Assert.isTrue(replyComment.getParentId().equals(parentId),
                        "提交的评论父评论与当前回复评论父评论不一致");
            }
            Assert.isTrue(replyComment.getFromUid().equals(comment.getToUid()),
                    "提交的评论回复的用户与当前回复评论的评论用户不一致");
            // 只能回复当前父评论及其子评论
            List<Integer> replyIdList = commentMapper.selectCommentIdByParentId(parentId);
            replyIdList.add(parentId);
            Assert.isTrue(replyIdList.contains(parentId), "当前父评论下不存在该子评论");
        });
    }

    /**
     * 评论通知
     *
     * @param comment      评论
     * @param fromNickname 评论用户昵称
     */
    private void notice(BComment comment, String fromNickname) {
        // 自己回复自己不用提醒
        if (comment.getFromUid().equals(comment.getToUid())) {
            return;
        }
        // 邮件标题
        String title = "约起评论通知!";
        // 回复用户id
        Integer toUid = BLOGGER_ID;
        if (Objects.isNull(comment.getParentId())) {    // 是父评论
            if (comment.getType().equals(RECORD.getType())) {
                // 查询记录信息
                BRecord record = recordMapper.selectOne(new LambdaQueryWrapper<BRecord>()
                        .select(BRecord::getTitle, BRecord::getUserId)
                        .eq(BRecord::getId, comment.getTypeId()));
                title = record.getTitle();
                toUid = record.getUserId();
            }
            if (comment.getType().equals(QUIZ.getType())) {
                // 查询题目信息
                BQuiz quiz = quizMapper.selectOne(new LambdaQueryWrapper<BQuiz>()
                        .select(BQuiz::getTitle, BQuiz::getUserId)
                        .eq(BQuiz::getId, comment.getTypeId()));
                title = quiz.getTitle();
                toUid = quiz.getUserId();
            }
            // 自己评论自己的记录或者题目，不用提醒
            if (comment.getFromUid().equals(toUid)) {
                return;
            }
        } else {    // 是子评论
            toUid = comment.getToUid();
            if (comment.getType().equals(RECORD.getType())) {
                // 查询记录标题
                title = recordMapper.selectOne(new LambdaQueryWrapper<BRecord>()
                                .select(BRecord::getTitle)
                                .eq(BRecord::getId, comment.getTypeId()))
                        .getTitle();
            }
            if (comment.getType().equals(QUIZ.getType())) {
                // 查询题目标题
                title = quizMapper.selectOne(new LambdaQueryWrapper<BQuiz>()
                                .select(BQuiz::getTitle)
                                .eq(BQuiz::getId, comment.getTypeId()))
                        .getTitle();
            }
        }
        // 查询回复用户邮箱、昵称、id
        BUser toUser = userMapper.selectOne(new LambdaQueryWrapper<BUser>()
                .select(BUser::getEmail, BUser::getNickname)
                .eq(BUser::getId, toUid));
        if (StringUtils.hasText(toUser.getEmail())) {   // 邮箱不为空
            sendEmail(comment, toUser, title, fromNickname);
        }
    }

    /**
     * 发送邮件
     *
     * @param comment      评论
     * @param toUser       回复用户信息
     * @param title        标题
     * @param fromNickname 评论用户昵称
     */
    private void sendEmail(BComment comment, BUser toUser, String title, String fromNickname) {
        MailDTO mailDTO = new MailDTO();
        if (comment.getIsCheck().equals(TRUE)) {
            Map<String, Object> contentMap = new HashMap<>(7);
            // 构造评论链接
            String typeId = Optional.ofNullable(comment.getTypeId())
                    .map(Object::toString)
                    .orElse("");
            String url = websiteUrl + getCommentPath(comment.getType()) + typeId;
            if (Objects.isNull(comment.getParentId())) {     // 父评论则提醒发表者
                mailDTO.setToEmail(toUser.getEmail());
                mailDTO.setSubject(COMMENT_REMIND);
                mailDTO.setTemplate(AUTHOR_TEMPLATE);
                String createTime = DateUtil.formatLocalDateTime(comment.getCreateTime());
                contentMap.put("time", createTime);
                contentMap.put("url", url);
                contentMap.put("title", title);
                contentMap.put("nickname", fromNickname);
                contentMap.put("content", comment.getContent());
                mailDTO.setContentMap(contentMap);
            } else {    // 子评论则提醒回复的用户
                BComment parentComment = commentMapper.selectOne(new LambdaQueryWrapper<BComment>()
                        .select(BComment::getContent, BComment::getCreateTime)
                        .eq(BComment::getId, comment.getReplyId()));
                mailDTO.setToEmail(toUser.getEmail());
                mailDTO.setSubject(COMMENT_REMIND);
                mailDTO.setTemplate(USER_TEMPLATE);
                contentMap.put("url", url);
                contentMap.put("title", title);
                String createTime = DateUtil.formatLocalDateTime(comment.getCreateTime());
                contentMap.put("time", createTime);
                // 被回复用户昵称
                contentMap.put("toUser", toUser.getNickname());
                // 评论用户昵称
                contentMap.put("fromUser", fromNickname);
                // 被回复的评论内容
                contentMap.put("parentComment", parentComment.getContent());
                // 回复评论内容
                contentMap.put("replyComment", comment.getContent());
                mailDTO.setContentMap(contentMap);
            }
            // 发送HTML邮件
            rabbitTemplate.convertAndSend(EMAIL_EXCHANGE, EMAIL_HTML_KEY, mailDTO);
        } else {
            // 审核提醒
            String adminEmail = userMapper.selectOne(new LambdaQueryWrapper<BUser>()
                    .select(BUser::getEmail)
                    .eq(BUser::getId, BLOGGER_ID)).getEmail();
            mailDTO.setToEmail(adminEmail);
            mailDTO.setSubject(CHECK_REMIND);
            mailDTO.setContent("您收到一条新的回复，请前往后台管理页面审核");
            // 发送普通邮件
            rabbitTemplate.convertAndSend(EMAIL_EXCHANGE, EMAIL_SIMPLE_KEY, mailDTO);
        }
    }
}




